﻿using System.Collections.Concurrent;
using Foundation.Caching;
using Foundation.Core;
using Nito.AsyncEx;

namespace Dyao.Book.Application.Extensions
{
    public static class DistributedMultiLevelCacheExtension
    {
        private static ConcurrentDictionary<string, SemaphoreSlim> _lockDict = new ConcurrentDictionary<string, SemaphoreSlim>();

        /// <summary>
        /// 缓存键前缀
        /// </summary>
        public static string Prefix = "MultiLevelCache";

        public static string BuildKey(string key) => Prefix + key;

        public static string BuildCacheKey(this IDistributedMultiLevelCache _, string key) => BuildKey(key);

        public async static Task<T> GetNullableAsync<T>(this IDistributedMultiLevelCache distributedMultiLevelCache, string cacheKey)
        {
            var key = BuildKey(cacheKey);
            var cache = await distributedMultiLevelCache.GetAsync<CacheBaseModel<T>>(key);
            if (cache.HasValue)
            {
                return cache.Value.Data;
            }

            return default(T);
        }

        public async static Task<T> GetOrAddAsync<T>(this IDistributedMultiLevelCache distributedMultiLevelCache, string cacheKey, Func<T> func, TimeSpan expireTimeSpan)
        {
            var key = BuildKey(cacheKey);
            var cache = await distributedMultiLevelCache.GetAsync<CacheBaseModel<T>>(key);
            if (cache.HasValue)
            {
                return cache.Value.Data;
            }

            var semaphore = _lockDict.GetOrAdd(cacheKey, (k) => new SemaphoreSlim(1, 1));
            using (await semaphore.LockAsync())
            {
                cache = await distributedMultiLevelCache.GetAsync<CacheBaseModel<T>>(key);
                if (cache.HasValue)
                {
                    return cache.Value.Data;
                }

                T t = func();
                CacheBaseModel<T> model = new CacheBaseModel<T> { Data = t };
                distributedMultiLevelCache.SetAsync(key, model, expireTimeSpan).GetAwaiter();

                return t;
            }
        }

        public async static Task<T> GetOrAddAsync<T>(this IDistributedMultiLevelCache distributedMultiLevelCache, string cacheKey, Func<Task<T>> func, TimeSpan expireTimeSpan)
        {
            var key = BuildKey(cacheKey);
            var cache = await distributedMultiLevelCache.GetAsync<CacheBaseModel<T>>(key);
            if (cache.HasValue)
            {
                return cache.Value.Data;
            }

            var semaphore = _lockDict.GetOrAdd(cacheKey, (k) => new SemaphoreSlim(1, 1));
            using (await semaphore.LockAsync())
            {
                cache = await distributedMultiLevelCache.GetAsync<CacheBaseModel<T>>(key);
                if (cache.HasValue)
                {
                    return cache.Value.Data;
                }

                T t = await func();
                CacheBaseModel<T> model = new CacheBaseModel<T> { Data = t };
                distributedMultiLevelCache.SetAsync(key, model, expireTimeSpan).GetAwaiter();

                return t;
            }
        }

        /// <summary>
        /// 获取或批量添加数据
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="distributedMultiLevelCache"></param>
        /// <param name="inputs"></param>
        /// <param name="keyFactory"></param>
        /// <param name="dataFactory"></param>
        /// <param name="dataMapper"></param>
        /// <param name="expireTimeSpan"></param>
        /// <param name="cacheNullData"></param>
        /// <returns></returns>
        public async static Task<IList<TResult>> GetOrAddListAsync<TInput, TResult>(this IDistributedMultiLevelCache distributedMultiLevelCache, IList<TInput> inputs, Func<TInput, string> keyFactory, Func<IList<TInput>, Task<IList<TResult>>> dataFactory, Func<TInput, IList<TResult>, TResult> dataMapper, TimeSpan expireTimeSpan, bool cacheNullData = true, TimeSpan? nullDataExpireTs = null)
        {
            var result = new List<TResult>(inputs.Count);
            var notExistsList = new List<TInput>(inputs.Count);

            //获取缓存数据
            foreach (var id in inputs)
            {
                var key = BuildKey(keyFactory(id));

                var cache = await distributedMultiLevelCache.GetAsync<CacheBaseModel<TResult>>(key);

                if (cache.HasValue)
                {
                    if (cache.Value.Data != null)
                    {
                        result.Add(cache.Value.Data);
                    }
                }
                else
                {
                    notExistsList.Add(id);
                }
            }

            // 加载未在缓存中的数据
            if (notExistsList.Count > 0)
            {
                var list = await dataFactory(notExistsList);

                if (!list.IsNullOrEmpty())
                {
                    result.AddRange(list);
                }

                foreach (var id in notExistsList)
                {
                    var data = dataMapper(id, list);

                    var isNull = data == null || object.Equals(data, default(TResult));

                    if (!isNull || cacheNullData) //设置缓存
                    {
                        var key = BuildKey(keyFactory(id));
                        var ts = !isNull ? expireTimeSpan : nullDataExpireTs.GetValueOrDefault(expireTimeSpan);
                        await distributedMultiLevelCache.SetAsync(key, new CacheBaseModel<TResult>() { Data = data }, ts);
                    }

                }
            }


            return result;
        }

    }

    public class CacheBaseModel<T>
    {
        public T Data { get; set; }
    }
}
